//-----------------------------------------------------------------------
// <copyright file="RegistrationSubscription.cs" company="iron9light">
//      Copyright (c) iron9light. All rights reserved.
// </copyright>
// <author>iron9light</author>
//-----------------------------------------------------------------------

namespace EventBrokerExtension
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Reflection;

    using Microsoft.Practices.ObjectBuilder2;
    using Microsoft.Practices.Unity;

    using Properties;

    public class RegistrationSubscription : InjectionMember
    {
        private readonly string methodName;

        private readonly string eventTopic;

        private readonly List<MethodParameter> methodParameters;

        /// <summary>
        /// Initializes a new instance of the <see cref="RegistrationSubscription"/> class.
        /// </summary>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="eventTopic">The event topic.</param>
        public RegistrationSubscription(string methodName, string eventTopic)
                : this(methodName, eventTopic, null)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="RegistrationSubscription"/> class.
        /// </summary>
        /// <param name="methodName">Name of the method.</param>
        /// <param name="eventTopic">The event topic.</param>
        /// <param name="methodParameters">The method parameters.</param>
        public RegistrationSubscription(string methodName, string eventTopic, params object[] methodParameters)
        {
            this.methodName = methodName;
            this.eventTopic = eventTopic;
            if (methodParameters != null)
            {
                this.methodParameters = Sequence.ToList(MethodParameter.ToParameters(methodParameters));               
            }
        }

        public override void AddPolicies(Type typeToCreate, string name, IPolicyList policies)
        {
            MethodInfo method = this.FindMethod(typeToCreate);
            GuardMethodExists(method, typeToCreate, this.methodName, this.methodParameters);
            GuardMethodNotStatic(method);
            GuardMethodNotGeneric(method);
            GuardMethodHasNoReturn(method);
            GuardMethodHasValidParams(method);

            SpecifiedSubscriptionSelectorPolicy selector = GetSelectorPolicy(typeToCreate, name, policies);

            selector.Add(method, this.eventTopic);
        }

        /// <summary>
        /// A small function to handle name matching. You can override this
        /// to do things like case insensitive comparisons.
        /// </summary>
        /// <param name="targetMethod">MethodInfo for the method you're checking.</param>
        /// <param name="nameToMatch">Name of the method you're looking for.</param>
        /// <returns>True if a match, false if not.</returns>
        protected virtual bool MethodNameMatches(MemberInfo targetMethod, string nameToMatch)
        {
            return targetMethod.Name == nameToMatch;
        }

        private static void GuardMethodExists(MethodInfo method, Type typeToCreate, string methodName, List<MethodParameter> methodParameters)
        {
            if (method == null)
            {
                string[] parameterNames;

                if (methodParameters != null)
                {
                    parameterNames = methodParameters.ConvertAll<string>(
                            delegate(MethodParameter parameter)
                            {
                                return parameter.ParameterTypeName;
                            }).ToArray();
                }
                else
                {
                    parameterNames = new string[0];
                }

                ThrowIllegalRegistrationSubscription(Resources.NoSuchMethod, typeToCreate, methodName, parameterNames);
            }
        }

        private static void GuardMethodNotStatic(MethodInfo method)
        {
            if (method.IsStatic)
            {
                ThrowIllegalRegistrationSubscription(Resources.CannotRegisterStaticMethod, method);
            }
        }

        private static void GuardMethodNotGeneric(MethodInfo method)
        {
            if (method.IsGenericMethodDefinition)
            {
                ThrowIllegalRegistrationSubscription(Resources.CannotRegisterOpenGenericMethod, method);
            }
        }

        private static void GuardMethodHasNoReturn(MethodInfo method)
        {
            if (method.ReturnType != typeof(void))
            {
                ThrowIllegalRegistrationSubscription(Resources.CannotRegisterMethodWithReturn, method);
            }
        }

        private static void GuardMethodHasValidParams(MethodInfo method)
        {
            if (!IsValidMethodParameters(method))
            {
                ThrowIllegalRegistrationSubscription(Resources.CannotRegisterMethodWithInvalidParams, method);
            }
        }

        private static SpecifiedSubscriptionSelectorPolicy GetSelectorPolicy(Type typeToInject, string name, IPolicyList policies)
        {
            NamedTypeBuildKey key = new NamedTypeBuildKey(typeToInject, name);
            ISubscriptionSelectorPolicy selector = policies.GetNoDefault<ISubscriptionSelectorPolicy>(key, false);
            if (selector == null || !(selector is SpecifiedSubscriptionSelectorPolicy))
            {
                selector = new SpecifiedSubscriptionSelectorPolicy();
                policies.Set(selector, key);
            }

            return (SpecifiedSubscriptionSelectorPolicy)selector;
        }

        private static bool IsValidMethod(MethodInfo method)
        {
            if (method.ReturnType != typeof(void))
            {
                return false;
            }

            return IsValidMethodParameters(method);
        }

        private static bool IsValidMethodParameters(MethodInfo method)
        {
            ParameterInfo[] parameters = method.GetParameters();

            if (parameters.Length != 2)
            {
                return false;
            }

            if (!typeof(object).IsAssignableFrom(parameters[0].ParameterType))
            {
                return false;
            }

            if (!typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType))
            {
                return false;
            }

            return true;
        }

        private static void ThrowIllegalRegistrationSubscription(string message, MethodBase method)
        {
            Type[] methodParameters = Array.ConvertAll<ParameterInfo, Type>(
                    method.GetParameters(),
                    delegate(ParameterInfo parameter)
                    {
                        return parameter.ParameterType;
                    });

            ThrowIllegalRegistrationSubscription(message, method.DeclaringType, method.Name, methodParameters);
        }

        private static void ThrowIllegalRegistrationSubscription(string message, Type typeToCreate, string methodName, IEnumerable<Type> methodParameters)
        {
            List<string> list = new List<string>();
            if (methodParameters != null)
            {
                foreach (Type parameter in methodParameters)
                {
                    list.Add(parameter.Name);
                }
            }

            ThrowIllegalRegistrationSubscription(message, typeToCreate, methodName, list.ToArray());
        }

        private static void ThrowIllegalRegistrationSubscription(string message, Type typeToCreate, string methodName, string[] parameterNames)
        {
            throw new InvalidOperationException(
                    string.Format(
                            CultureInfo.CurrentCulture,
                            message,
                            typeToCreate.Name,
                            methodName,
                            string.Join(", ", parameterNames)));
        }

        private MethodInfo FindMethod(Type typeToCreate)
        {
            if (this.methodParameters != null && this.methodParameters.Count > 0)
            {
                return this.FindMethodWithParameters(typeToCreate);
            }

            return this.FindDefaultMethod(typeToCreate);
        }

        private MethodInfo FindDefaultMethod(Type typeToCreate)
        {
            foreach (MethodInfo method in typeToCreate.GetMethods())
            {
                if (this.MethodNameMatches(method, this.methodName) && IsValidMethod(method))
                {
                    return method;
                }
            }

            return null;
        }

        private MethodInfo FindMethodWithParameters(Type typeToCreate)
        {
            ParameterMatcher matcher = new ParameterMatcher(this.methodParameters);
            foreach (MethodInfo method in typeToCreate.GetMethods())
            {
                if (this.MethodNameMatches(method, this.methodName))
                {
                    if (matcher.Matches(method.GetParameters()))
                    {
                        return method;
                    }
                }
            }

            return null;
        }
    }
}